package liquidfs.data.ops;

import java.io.*;

import liquidfs.*;
import liquidfs.core.*;
import liquidfs.data.*;
import liquidfs.data.ex.*;
import liquidfs.data.fs.*;

public class ReadFileOperation extends AbstractFileOperation<Integer> {
   
   private long offset;
   
	public ReadFileOperation(PeerDevice origin, String filepath, long offset, int length) {
		super(origin, filepath);
		this.offset = offset;
		this.setMaximumOutputSize(length);
	}

	@Override
	public Integer execute(Application app, InputStream in, OutputStream out) {
      
      // Create a buffer for the incoming data
      byte[] buffer = new byte[this.getMaximumOutputSize()];
	   
	   // Get a reference to the file system
		LiquidFileSystem fs = app.getFileSystem();
		
		// If the file is local, just read it
		if (fs.isLocal(this.getFilePath())) {
		   
		   // Get the file on our local disk
		   File file = fs.getLocalFile(this.getFilePath());       
         RandomAccessFile disk;
   		
         // Keep track of how much we read
         int read;
         
   		// Open it
         try {
            disk = new RandomAccessFile(file, "r");
         }
         catch (java.io.FileNotFoundException e) {
            throw new liquidfs.data.ex.FileNotFoundException("Unable to find the file " + this.getFilePath() + ": " + e.getLocalizedMessage());
         }
   		   
   		// Read a block of bytes
   		try {
   		   disk.seek(offset);
   		   read = disk.read(buffer, 0, this.getMaximumOutputSize());
   		   out.write(buffer);
   		}
   		catch (IOException e) {
   		   throw new FileSystemException("Unable to read the file " + this.getFilePath() + ": " + e.getLocalizedMessage());
   		}
   		finally {
            try {
               disk.close();
            }
            catch (IOException e) {
               throw new FileSystemException("Unable to properly close file " + this.getFilePath() + ".");
            }
         }
   		
   		return read;
		}
		
		// We might get multiple exceptions from multiple hosts, so we'll need to keep track of them all somewhere
		AmalgomatedFileSystemException exceptions = null;
		
		// If it isn't local, iterate through each device we're connected two, except the ones that have already been queried
      for (Link link : app.getConfiguration().getLinks()) {
         
         // Skip devices that have already been queried
         if (this.reached(link.getDevice())) {
            continue;
         }
         
         // Pass this message along to other links in the network
         Result<Integer> result = fs.relay(link, this, null, out);
         
         try {
            Integer read = result.getData();
            
            // If we read 0, that's not an exception, just a normal response that we will interpret later as being a successful read of the end of the file
            //if (read <= 0) 
            //   continue;
            
            return read;
         }
         // If the device did not respond
         catch (CommunicationException e) {
            
            // If we haven't created the exception object yet, do so now
            if (exceptions == null)
               exceptions = new AmalgomatedFileSystemException();
            
            // Add the exception to the link
            exceptions.addException(app.getConfiguration().getLocalLink(), e);
            
            // Move along to the next host
            continue;
         }
         // If the device responded with an exception
         catch (FileSystemException e) {
            
            // If we haven't created the exception object yet, do so now
            if (exceptions == null)
               exceptions = new AmalgomatedFileSystemException();
            
            // Add the exception to the link
            exceptions.addException(link, e);
            
            // Move along to the next host
            continue;
         }
      }
      
      // If we have some exceptions, throw them
      if (exceptions != null)
         throw exceptions;
      
      // If there are no hosts left to query, then throw an exception
      throw new FileSystemException("Unable to find the file " + this.getFilePath() + ".");
	}
}
